package com.ikingtech.framework.sdk.plugin.core;

import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.utils.Tools;
import lombok.NonNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author tie yan
 */
public abstract class AbstractPluginRegistrar implements ApplicationContextAware, PluginRegistrar {

    protected DefaultListableBeanFactory beanFactory;

    protected ApplicationContext applicationContext;

    protected final ClassLoader classLoader = AbstractPluginRegistrar.class.getClassLoader();

    protected final Map<String, Map<String, Class<?>>> loadedClassMap = new ConcurrentHashMap<>();

    protected final Map<String, List<String>> registeredBeanMap = new ConcurrentHashMap<>();

    public void loadJarClass(String jarFileName, List<PluggableClass> classes) {
        URL url;
        try {
            url = new File(jarFileName).toURI().toURL();
        } catch (IOException e) {
            throw new FrameworkException(Tools.Str.format("获取jar文件URL失败[{}]", e.getCause().getMessage()));
        }
        try {
            Method addUrl = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            addUrl.setAccessible(true);
            addUrl.invoke(this.classLoader, url);
        } catch (Exception e) {
            throw new FrameworkException(Tools.Str.format("加载jar文件URL失败[{}]", e.getCause().getMessage()));
        }
        Map<String, Class<?>> classMap = new HashMap<>();
        try {
            for (PluggableClass clazz : classes) {
                Class<?> loadedClass = this.classLoader.loadClass(clazz.getName());
                classMap.put(clazz.getName(), loadedClass);
            }
        } catch (ClassNotFoundException e) {
            throw new FrameworkException(Tools.Str.format("加载class文件失败[{}]", e.getCause().getMessage()));
        }
        this.loadedClassMap.put(jarFileName, classMap);
        try {
            this.classLoader.getResources("com/iking/plugin/http/supplier/");
        } catch (IOException e) {
            throw new FrameworkException(Tools.Str.format("加载class文件失败[{}]", e.getCause().getMessage()));
        }
    }

    public void unloadJarClass(String jarFileName) {
        Map<String, Class<?>> classMap = this.loadedClassMap.get(jarFileName);
        for (Class<?> clazz : classMap.values()) {
            clazz = null;
        }
    }

    public void registerBean(String jarFileName, String beanName, Class<?> clazz) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON);
        this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
        this.registeredBeanMap.computeIfAbsent(jarFileName, key -> new ArrayList<>()).add(beanName);
    }

    public void unregisterBean(String jarFileName) {
        this.registeredBeanMap.get(jarFileName).forEach(this.beanFactory::removeBeanDefinition);
        this.registeredBeanMap.remove(jarFileName);
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        this.beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
    }
}
